package org.myspringframework.core.aop;

import org.myspringframework.core.BeanContainer;
import org.myspringframework.core.annotation.Aspect;
import org.myspringframework.core.annotation.Order;
import org.myspringframework.core.aop.aspect.AspectWrapper;
import org.myspringframework.core.aop.aspect.DefaultAspect;
import org.myspringframework.core.util.CollectionUtils;

import java.lang.annotation.Annotation;
import java.util.*;

/**
 * @author jaymin.<br>
 * aop织入器.<Br>
 * 2021/2/24 21:42
 */
public class AspectWeaver {

    private BeanContainer beanContainer;

    /**
     * 在实例化织入器的同时，将容器进行注入
     */
    public AspectWeaver() {
        beanContainer = BeanContainer.getInstance();
    }

    /**
     * <p> 1. 获取所有的切面类
     * <p> 2. 将切面类按照不同的织入目标进行切分
     * <p> 3. 按照不同的织入目标分别去按序织入Aspect的逻辑
     */
    public void doAop() {
        Set<Class<?>> aspectSet = beanContainer.getClassesByAnnotation(Aspect.class);
        HashMap<Class<? extends Annotation>, List<AspectWrapper>> categorizedMap = new HashMap<>();
        if (CollectionUtils.isEmpty(aspectSet)) {
            return;
        }
        for (Class<?> aspectClass : aspectSet) {
            if (!verifyAspect(aspectClass)) {
                throw new RuntimeException("@Aspect and @Order have not bean added to the Aspect class," +
                        "or Aspect class does not extend from DefaultAspect,or the value in Aspect Tag equals @Aspect");
            }
            categorizeAspect(categorizedMap, aspectClass);
        }
        if (CollectionUtils.isEmpty(categorizedMap)) {
            return;
        }
        for (Map.Entry<Class<? extends Annotation>, List<AspectWrapper>> category : categorizedMap.entrySet()) {
            weaveByCategory(category.getKey(), category.getValue());
        }
    }

    /**
     * 1. 获取被代理类的集合 <br>
     * 2. 遍历被代理类，分别为每个被代理类生成动态代理实例. <br>
     * 3. 将动态代理对象实例添加到容器中，取代未被代理前的类实例.<br>
     *
     * @param category
     * @param aspectWrapperList
     */
    private void weaveByCategory(Class<? extends Annotation> category, List<AspectWrapper> aspectWrapperList) {
        Set<Class<?>> classSet = beanContainer.getClassesByAnnotation(category);
        if (CollectionUtils.isEmpty(classSet)) {
            return;
        }
        classSet.forEach(targetClass -> {
            AspectListExecutor aspectListExecutor = new AspectListExecutor(targetClass, aspectWrapperList);
            Object proxyBean = ProxyCreator.createProxy(targetClass, aspectListExecutor);
            beanContainer.addBean(targetClass, proxyBean);
        });
    }

    /**
     * 按aspect对AspectWrapper进行归类,可以用lambda进行重写
     *
     * @param categorizedMap
     * @param aspectClass
     */
    private void categorizeAspect(HashMap<Class<? extends Annotation>, List<AspectWrapper>> categorizedMap, Class<?> aspectClass) {
        Order orderTag = aspectClass.getAnnotation(Order.class);
        Aspect aspectTag = aspectClass.getAnnotation(Aspect.class);
        DefaultAspect aspect = (DefaultAspect) beanContainer.getBean(aspectClass);
        AspectWrapper aspectWrapper = AspectWrapper.builder().orderIndex(orderTag.value()).aspectObject(aspect).build();
        if (!categorizedMap.containsKey(aspectTag.value())) {
            // 如果织入的joinpoint第一次出现，则以该joinpoint为key,以新创建的List<AspectWrapper>为value.
            List<AspectWrapper> aspectWrapperList = new ArrayList<>();
            aspectWrapperList.add(aspectWrapper);
            categorizedMap.put(aspectTag.value(), aspectWrapperList);
        } else {
            // 如果织入的joinpoint不是第一次出现,则往joinpoint对应的value中添加新的Aspect逻辑
            List<AspectWrapper> aspectWrapperList = categorizedMap.get(aspectTag.value());
            aspectWrapperList.add(aspectWrapper);

        }
    }

    /**
     * 框架中一定要遵守Aspect和@Order的规范，同时必须继承自DefaultAspect.class.<br>
     * 此外。@Aspect的属性不能是它本身
     *
     * @param aspectClass 切面类
     * @return
     */
    private boolean verifyAspect(Class<?> aspectClass) {
        return aspectClass.isAnnotationPresent(Aspect.class) &&
                aspectClass.isAnnotationPresent(Order.class) &&
                DefaultAspect.class.isAssignableFrom(aspectClass) &&
                aspectClass.getAnnotation(Aspect.class).value() != Aspect.class;
    }
}
